package com.nullprogram.chess.ai;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

import com.nullprogram.chess.Board;
import com.nullprogram.chess.Game;
import com.nullprogram.chess.Move;
import com.nullprogram.chess.MoveList;
import com.nullprogram.chess.Piece;
import com.nullprogram.chess.Position;
import com.nullprogram.chess.Piece.Side;
import com.nullprogram.chess.pieces.Archbishop;
import com.nullprogram.chess.pieces.Bishop;
import com.nullprogram.chess.pieces.Chancellor;
import com.nullprogram.chess.pieces.King;
import com.nullprogram.chess.pieces.Knight;
import com.nullprogram.chess.pieces.Pawn;
import com.nullprogram.chess.pieces.Queen;
import com.nullprogram.chess.pieces.Rook;
import com.nullprogram.chess.Player;

public class AlphaBeta implements Player {

	// search depth
	int maxDepth;

	// current game
	Game game;

	// piece values
	HashMap<Class, Double> pieceValues;

	// piece counts
	HashMap<String, Integer> pieceCounts;
	HashMap<String, Integer> friendCounts;
	HashMap<String, Integer> enemyCounts;

	// side of this ai
	Side side = null;

	int t = -1;
	
	double[] pdVector = new double[6];
	ArrayList<Double> P = new ArrayList<Double>();
	ArrayList<HashMap<String,Double>> wPk = new ArrayList<HashMap<String,Double>>();
	final double LEARN_RATE = .001;
	final double DISCOUNT = .7;
	boolean td = true;

	// Bonus multiplier to states that cause check
	final double CHECK_MULTIPLIER = 2;

	public AlphaBeta(final Game active) {
		this(active, "default");
	}

	public AlphaBeta(final Game active, final String name) {
		this(active, getConfig(name));
	}

	public AlphaBeta(final Game game, final Properties props) {
		this.game = game;

		pieceValues = new HashMap<Class, Double>();
		Properties config = props;

		/* Piece values */
		pieceValues.put((new Pawn(side)).getClass(),
				Double.parseDouble(config.getProperty("Pawn")));
		pieceValues.put((new Knight(side)).getClass(),
				Double.parseDouble(config.getProperty("Knight")));
		pieceValues.put((new Bishop(side)).getClass(),
				Double.parseDouble(config.getProperty("Bishop")));
		pieceValues.put((new Rook(side)).getClass(),
				Double.parseDouble(config.getProperty("Rook")));
		pieceValues.put((new Queen(side)).getClass(),
				Double.parseDouble(config.getProperty("Queen")));
		pieceValues.put((new King(side)).getClass(),
				Double.parseDouble(config.getProperty("King")));
		pieceValues.put((new Chancellor(side)).getClass(),
				Double.parseDouble(config.getProperty("Chancellor")));
		pieceValues.put((new Archbishop(side)).getClass(),
				Double.parseDouble(config.getProperty("Archbishop")));

		maxDepth = (int) Double.parseDouble(config.getProperty("depth"));
	}

	@Override
	public Move takeTurn(Board board, Side side) {

		this.side = side;
		MoveList moves = board.allMoves(side, true);
		moves.shuffle(); // We should sort the moves by what we think will be
							// best
							// e.g. Do captures first, may result in more
							// pruning

		long startTime = System.currentTimeMillis();

		Move bestMove = null;
		for (final Move move : moves) {
			final Board callboard = board.copy();
			callboard.move(move);

			double beta = Double.POSITIVE_INFINITY;
			if (bestMove != null) {
				beta = -bestMove.getScore();
			}

			double value = alphaBeta(callboard, maxDepth - 1,
					Piece.opposite(side), Double.NEGATIVE_INFINITY, beta);
			move.setScore(-value);

			if (bestMove == null || move.getScore() > bestMove.getScore()) {
				bestMove = move;
			}
		}

		// ----------------------- TD LEARNING -------------------------------
		t++;
		P.add(1/(1+Math.exp(-bestMove.getScore())));
		wPk.add(calculatePartialDerivatives());
		
		
		// --------------------- END TD LEARNING -----------------------------
		return bestMove;
	}

	private HashMap<String,Double> calculatePartialDerivatives(){
		HashMap<String,Double> wPk = new HashMap<String,Double>();
		
		Iterator i = pieceCounts.keySet().iterator();
		
		
		double exponent = pieceValues.get(Pawn.class)*(friendCounts.get("Pawn")-enemyCounts.get("Pawn"))
				+pieceValues.get(Bishop.class)*(friendCounts.get("Bishop")-enemyCounts.get("Bishop"))
				+pieceValues.get(Knight.class)*(friendCounts.get("Knight")-enemyCounts.get("Knight"))
				+pieceValues.get(Rook.class)*(friendCounts.get("Rook")-enemyCounts.get("Rook"))
				+pieceValues.get(Queen.class)*(friendCounts.get("Queen")-enemyCounts.get("Queen"))
				+pieceValues.get(King.class)*(friendCounts.get("King")-enemyCounts.get("King"));
		
		//System.out.print("Exponent: "+exponent+", ");
		
		double eValue = Math.exp(exponent);
		
		//System.out.print("eValue: "+eValue+", ");
		
		while(i.hasNext()){
			String pieceName = (String)i.next();
			
			double value = (friendCounts.get(pieceName)-enemyCounts.get(pieceName))*eValue/Math.pow(eValue+1, 2);
			
			//System.out.println(pieceName+" wPk: "+value);
			
			wPk.put(pieceName, value);
		}
		
		return wPk;
	}
	


	private double alphaBeta(final Board b, final int depth, final Side s,
			final double alpha, final double beta) {
		if (depth == 0) {
			double value = evaluate(b);
			return (s != side) ? -value : value;
		}

		Side opposite = Piece.opposite(s);
		double best = alpha;
		MoveList list = b.allMoves(s, true);
		for (Move move : list) {
			b.move(move);
			best = Math.max(best,
					-alphaBeta(b, depth - 1, opposite, -beta, -best));
			b.undo();

			if (beta <= best)
				return best;
		}

		return best;
	}

	private double evaluate(final Board b) {		
		// Need to manually remove the king
		// If the king is never taken, its value is never updated
		
		double mValue;
		
		if(b.checkmate(Side.WHITE)){
			Piece p = b.getPiece(b.findKing(Side.WHITE));
			b.setPiece(b.findKing(Side.WHITE), null);
			mValue = materialValue(b);
			b.setPiece(p.getPosition(), p);
		}else if(b.checkmate(Side.BLACK)){
			Piece p = b.getPiece(b.findKing(Side.BLACK));
			b.setPiece(b.findKing(Side.BLACK), null);
			mValue = materialValue(b);
			b.setPiece(p.getPosition(), p);
		}else{
			mValue = materialValue(b);
		}


		return mValue;
	}

	// Get the material value
	// and count the pieces while we're at it
	private double materialValue(final Board b) {
		pieceCounts = new HashMap<String, Integer>();
		pieceCounts.put("Pawn", 0);
		pieceCounts.put("Rook", 0);
		pieceCounts.put("Knight", 0);
		pieceCounts.put("Bishop", 0);
		pieceCounts.put("King", 0);
		pieceCounts.put("Queen", 0);
		
		friendCounts = new HashMap<String, Integer>();
		friendCounts.put("Pawn", 0);
		friendCounts.put("Rook", 0);
		friendCounts.put("Knight", 0);
		friendCounts.put("Bishop", 0);
		friendCounts.put("King", 0);
		friendCounts.put("Queen", 0);
		
		enemyCounts = new HashMap<String, Integer>();
		enemyCounts.put("Pawn", 0);
		enemyCounts.put("Rook", 0);
		enemyCounts.put("Knight", 0);
		enemyCounts.put("Bishop", 0);
		enemyCounts.put("King", 0);
		enemyCounts.put("Queen", 0);


		double value = 0;
		for (int y = 0; y < b.getHeight(); y++) {
			for (int x = 0; x < b.getWidth(); x++) {
				Position pos = new Position(x, y);
				Piece p = b.getPiece(pos);
				if (p != null) {
					value += pieceValues.get(p.getClass())
							* p.getSide().value();
					Integer i = pieceCounts.get(p.toString());
					if (i != null)
						pieceCounts.put(p.toString(), new Integer(
								i.intValue() + 1));
					
					if(p.getSide() == this.side){
						i = friendCounts.get(p.toString());
						if (i != null)
							friendCounts.put(p.toString(), new Integer(
									i.intValue() + 1));
					}else{
						i = enemyCounts.get(p.toString());
						if (i != null)
							enemyCounts.put(p.toString(), new Integer(
									i.intValue() + 1));
					}
				}
			}
		}
		return value * side.value();
	}

	public static Properties getConfig(final String name) {
		Properties props;
		if ("default".equals(name)) {
			props = new Properties();
		} else {
			props = new Properties(getConfig("default"));
		}

		String filename = name + ".properties";
		InputStream in = Minimax.class.getResourceAsStream(filename);
		try {
			props.load(in);
		} catch (java.io.IOException e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return props;
	}

	public void setMaxDepth(int d) {
		maxDepth = d;
	}

	public void setPieceValues(HashMap<Class, Double> values) {
		pieceValues = values;
	}

	public HashMap<Class, Double> getPieceValues() {
		return pieceValues;
	}

	public void setTD(boolean td) {
		this.td = td;
	}
	
	public ArrayList<HashMap<String,Double>> getWPK(){
		return wPk;
	}
	
	public ArrayList<Double> getP(){
		return P;
	}
}
